package org.terramagnet.workflow.dynamic.databinding;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.terramagnet.workflow.dynamic.databinding.type.BooleanType;
import org.terramagnet.workflow.dynamic.databinding.type.DateType;
import org.terramagnet.workflow.dynamic.databinding.type.DoubleType;
import org.terramagnet.workflow.dynamic.databinding.type.EnumType;
import org.terramagnet.workflow.dynamic.databinding.type.FloatType;
import org.terramagnet.workflow.dynamic.databinding.type.IntegerType;
import org.terramagnet.workflow.dynamic.databinding.type.LongType;
import org.terramagnet.workflow.dynamic.databinding.type.StringType;

/**
 * 数据类型工厂.
 *
 * @author terrason
 */
public final class DataTypeBuilder {

    public static final Pattern DATE_TYPEDEF = Pattern.compile("(date|time|datetime|timestamp|日期)(\\((.*)\\))?");
    public static final Pattern DOUBLE_TYPEDEF = Pattern.compile("(double)(\\((.*)\\))?");
    public static final Pattern FLOAT_TYPEDEF = Pattern.compile("(float)(\\((.*)\\))?");
    public static final Pattern INTEGER_TYPEDEF = Pattern.compile("(int|integer)(\\((.*)\\))?");
    public static final Pattern LONG_TYPEDEF = Pattern.compile("(long)(\\((.*)\\))?");
    public static final Pattern STRING_TYPEDEF = Pattern.compile("(char|varchar|string)(\\((.*)\\))?");
    public static final Pattern BOOLEAN_TYPEDEF = Pattern.compile("(boolean)(\\((.*)\\))?");
    public static final Pattern ENUM_TYPEDEF = Pattern.compile("(.*)(\\((.*)\\))?");

    /**
     * 根据类型定义语句生成数据类型实例. 不能识别的定义模式返回{@code null}。
     *
     * @param typeDef 类型定义语句
     * @return 数据类型或{@code null}
     */
    public static DataType init(String typeDef) {
        Matcher matcher = DATE_TYPEDEF.matcher(typeDef);
        if (matcher.matches()) {
            String option = matcher.group(3);
            DateType dateType = createDateType();
            if (StringUtils.isBlank(option)) {
                String s = matcher.group(1);
                if ("date".equals(s)) {
                    option = "yyyy-MM-dd";
                } else if ("time".equals(s)) {
                    option = "HH:mm:ss";
                } else if ("timestamp".equals(s)) {
                    option = "yyyy-MM-dd HH:mm:ss";
                } else {
                    option = "yyyy-MM-dd-HH.mm.ss.SSS000";
                }
            }
            dateType.setFormat(option);
            dateType.setLength(option.length());
            return dateType;
        }
        matcher = DOUBLE_TYPEDEF.matcher(typeDef);
        if (matcher.matches()) {
            DoubleType doubleType = new DoubleType();
            String option = matcher.group(3);
            if (StringUtils.isBlank(option)) {
                option = "309,308";
            }
            String[] args = option.split(",");
            int length = NumberUtils.toInt(args[0].trim(), 309);
            int precision = args.length > 1 ? NumberUtils.toInt(args[1].trim(), 308) : 308;
            doubleType.setLength(length);
            doubleType.setPrecision(precision);
            return doubleType;
        }
        matcher = FLOAT_TYPEDEF.matcher(typeDef);
        if (matcher.matches()) {
            FloatType floatType = new FloatType();
            String option = matcher.group(3);
            if (StringUtils.isBlank(option)) {
                option = "39,38";
            }
            String[] args = option.split(",");
            int length = NumberUtils.toInt(args[0].trim(), 39);
            int precision = args.length > 1 ? NumberUtils.toInt(args[1].trim(), 38) : 38;
            floatType.setLength(length);
            floatType.setPrecision(precision);
            return floatType;
        }
        matcher = LONG_TYPEDEF.matcher(typeDef);
        if (matcher.matches()) {
            LongType longType = createLongType();
            String option = matcher.group(3);
            if (StringUtils.isBlank(option)) {
                option = "19";
            }
            int length = NumberUtils.toInt(option.trim(), 19);
            longType.setLength(length);
            return longType;
        }
        matcher = INTEGER_TYPEDEF.matcher(typeDef);
        if (matcher.matches()) {
            IntegerType integerType = createIntegerType();
            String option = matcher.group(3);
            if (StringUtils.isBlank(option)) {
                option = "10";
            }
            int length = NumberUtils.toInt(option.trim(), 10);
            integerType.setLength(length);
            return integerType;
        }
        matcher = STRING_TYPEDEF.matcher(typeDef);
        if (matcher.matches()) {
            StringType stringType = createStringType();
            String option = matcher.group(3);
            if (StringUtils.isBlank(option)) {
                option = "255";
            }
            int length = NumberUtils.toInt(option.trim(), 255);
            stringType.setLength(length);
            return stringType;
        }
        matcher = BOOLEAN_TYPEDEF.matcher(typeDef);
        if (matcher.matches()) {
            return createBooleanType();
        }
        matcher = ENUM_TYPEDEF.matcher(typeDef);
        if (matcher.matches()) {
            EnumType enumType = new EnumType();
            String caption = matcher.group(1);
            enumType.setCaption(caption);
            String option = matcher.group(3);
            if (StringUtils.isNotBlank(option)) {
                enumType.setOptions(option);
                return enumType;
            }
        }
        return null;
    }

    public static DateType createDateType() {
        return new DateType();
    }

    public static DoubleType createDoubleType() {
        DoubleType type = new DoubleType();
        type.setLength(309);
        type.setPrecision(308);
        return type;
    }

    public static FloatType createFloatType() {
        FloatType type = new FloatType();
        type.setLength(39);
        type.setPrecision(38);
        return type;
    }

    public static LongType createLongType() {
        LongType type = new LongType();
        type.setLength(19);
        return type;
    }

    public static IntegerType createIntegerType() {
        IntegerType type = new IntegerType();
        type.setLength(10);
        return type;
    }

    public static StringType createStringType() {
        StringType type = new StringType();
        type.setLength(255);
        return type;
    }

    public static BooleanType createBooleanType() {
        return new BooleanType();
    }
}
